﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Transactions;

namespace SystemHelperLibrary.API.FilesExt
{

	[ComImport]
	[Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	public interface IKernelTransaction
	{
		void GetHandle(out IntPtr ktmHandle);
	}

	public static class FileHelper
	{
		#region const
		internal const uint FILE_SHARE_READ = 0x00000001;
		internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;

		internal const int FILE_BEGIN = 0;
		internal const int FILE_CURRENT = 1;
		internal const int FILE_END = 2;

		//Constants for dwCreationDisposition:
		internal const uint CREATE_NEW = 1;
		internal const uint CREATE_ALWAYS = 2;
		internal const uint OPEN_EXISTING = 3;
		internal const uint OPEN_ALWAYS = 4;
		internal const uint TRUNCATE_EXISTING = 5;
		private const uint FILE_SHARE_NONE = 0x0;

		internal const UInt32 ERROR_FILE_NOT_FOUND = 2;
		internal const UInt32 ERROR_INVALID_NAME = 123;
		internal const UInt32 ERROR_ACCESS_DENIED = 5;
		internal const UInt32 ERROR_INVALID_HANDLE = 6;
		internal const UInt32 ERROR_IO_PENDING = 997;
		internal const UInt32 ERROR_HANDLE_EOF = 38;

		const UInt32 WAIT_OBJECT_0 = 0x00000000;
		const UInt32 WAIT_ABANDONED = 0x00000080;
		const UInt32 WAIT_ABANDONED_0 = 0x00000080;

		//Constants for return value:
		internal const Int32 INVALID_HANDLE_VALUE = -1;

		//Constants for dwFlagsAndAttributes:
		const UInt32 FILE_FLAG_OVERLAPPED = 0x40000000;

		//Constants for dwDesiredAccess:
		internal const UInt32 GENERIC_READ = 0x80000000;
		internal const UInt32 GENERIC_WRITE = 0x40000000;

		internal const UInt32 FILE_FLAG_WRITE_THROUGH = 0x80000000;
		internal const UInt32 FILE_FLAG_NO_BUFFERING = 0x20000000;
		internal const UInt32 FILE_FLAG_RANDOM_ACCESS = 0x10000000;
		internal const UInt32 FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000;
		internal const UInt32 FILE_FLAG_DELETE_ON_CLOSE = 0x4000000;
		internal const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
		internal const UInt32 FILE_FLAG_POSIX_SEMANTICS = 0x1000000;

		// Constants for dwEvtMask:
		internal const UInt32 EV_RXCHAR = 0x0001;
		internal const UInt32 EV_RXFLAG = 0x0002;
		internal const UInt32 EV_TXEMPTY = 0x0004;
		internal const UInt32 EV_CTS = 0x0008;
		internal const UInt32 EV_DSR = 0x0010;
		internal const UInt32 EV_RLSD = 0x0020;
		internal const UInt32 EV_BREAK = 0x0040;
		internal const UInt32 EV_ERR = 0x0080;
		internal const UInt32 EV_RING = 0x0100;
		internal const UInt32 EV_PERR = 0x0200;
		internal const UInt32 EV_RX80FULL = 0x0400;
		internal const UInt32 EV_EVENT1 = 0x0800;
		internal const UInt32 EV_EVENT2 = 0x1000;

		// Added to enable use of "return immediately" timeout.
		const UInt32 MAXDWORD = 0xffffffff;

		internal const UInt16 PURGE_TXABORT = 0x0001;    // Kill the pending/current writes to the comm port.
		internal const UInt16 PURGE_RXABORT = 0x0002;    // Kill the pending/current reads to the comm port.
		internal const UInt16 PURGE_TXCLEAR = 0x0004;    // Kill the transmit queue if there.
		internal const UInt16 PURGE_RXCLEAR = 0x0008;    // Kill the typeahead buffer if there.
		#endregion

		[StructLayout(LayoutKind.Sequential)]
		internal struct OVERLAPPED
		{
			internal UIntPtr internalLow;
			internal UIntPtr internalHigh;
			internal UInt32 offset;
			internal UInt32 offsetHigh;
			internal IntPtr hEvent;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct SECURITY_ATTRIBUTES
		{
			internal Int32 nLength;
			internal IntPtr lpSecurityDescriptor;
			internal bool bInheritHandle;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct PROCESS_INFORMATION
		{
			internal IntPtr hProcess;
			internal IntPtr hThread;
			internal int dwProcessId;
			internal int dwThreadId;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct FILETIME
		{
			public int dwLowDateTime;
			public int dwHighDateTime;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct DCB
		{
			public int DCBlength;
			public int BaudRate;
			/*
			public int fBinary;
			public int fParity;
			public int fOutxCtsFlow;
			public int fOutxDsrFlow;
			public int fDtrControl;
			public int fDsrSensitivity;
			public int fTXContinueOnXoff;
			public int fOutX;
			public int fInX;
			public int fErrorChar;
			public int fNull;
			public int fRtsControl;
			public int fAbortOnError;
			public int fDummy2;
			*/
			public uint flags;
			public ushort wReserved;
			public ushort XonLim;
			public ushort XoffLim;
			public byte ByteSize;
			public byte Parity;
			public byte StopBits;
			public char XonChar;
			public char XoffChar;
			public char ErrorChar;
			public char EofChar;
			public char EvtChar;
			public ushort wReserved1;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct MEMORY_STATUS
		{
			public int dwLength;
			public int dwMemoryLoad;
			public int dwTotalPhys;
			public int dwAvailPhys;
			public int dwTotalPageFile;
			public int dwAvailPageFile;
			public int dwTotalVirtual;
			public int dwAvailVirtual;
		}

		public enum FileType : uint
		{
			FileTypeChar = 0x0002,
			FileTypeDisk = 0x0001,
			FileTypePipe = 0x0003,
			FileTypeRemote = 0x8000,
			FileTypeUnknown = 0x0000,
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct OFSTRUCT
		{
			public byte cBytes;
			public byte fFixedDisc;
			public UInt16 nErrCode;
			public UInt16 Reserved1;
			public UInt16 Reserved2;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
			public string szPathName;
		}

		internal enum OpenFileFlags : uint
		{
			OF_READ = 0x00000000,
			OF_WRITE = 0x00000001,
			OF_READWRITE = 0x00000002,
			OF_SHARE_COMPAT = 0x00000000,
			OF_SHARE_EXCLUSIVE = 0x00000010,
			OF_SHARE_DENY_WRITE = 0x00000020,
			OF_SHARE_DENY_READ = 0x00000030,
			OF_SHARE_DENY_NONE = 0x00000040,
			OF_PARSE = 0x00000100,
			OF_DELETE = 0x00000200,
			OF_VERIFY = 0x00000400,
			OF_CANCEL = 0x00000800,
			OF_CREATE = 0x00001000,
			OF_PROMPT = 0x00002000,
			OF_EXIST = 0x00004000,
			OF_REOPEN = 0x00008000,
		}

		[Flags]
		internal enum ReplaceFileFlags : uint
		{
			REPLACEFILE_WRITE_THROUGH = 0x00000001,
			REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002
		}

		[DllImport("Advapi32.dll", CharSet = CharSet.Auto)]
		internal static extern Boolean FileEncryptionStatus(String filename, out UInt32 status);

		[DllImport("Kernel32", CharSet = CharSet.Auto)]
		internal static extern Int32 GetShortPathName(string path, // input string
														ref StringBuilder shortPath,    // output string
														int shortPathLength);     // StringBuilder.Capacity

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool GetFileTime(IntPtr hFile, ref FILETIME lpCreationTime, ref FILETIME lpLastAccessTime,
												ref FILETIME lpLastWriteTime);


		[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
		internal static extern Int32 CompareFileTime(FILETIME ft1, FILETIME ft2);

		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		internal static extern IntPtr CreateFile(String lpFileName, uint dwDesiredAccess, uint dwShareMode,
												ref SECURITY_ATTRIBUTES lpSecurityAttributes, uint dwCreationDisposition,
												uint dwFlagsAndAttributes, IntPtr hTemplateFile);

		[DllImport("kernel32.dll")]
		internal static extern IntPtr CreateFileTransacted(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes,
															uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile,
															IntPtr hHandle, IntPtr pusMiniVersion, IntPtr pExtendedParameter);

		internal delegate uint CopyProgressRoutineHandler(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred,
			  uint dwStreamNumber, uint dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);//LPVOID;
		[DllImport("kernel32.dll")]
		internal static extern bool CopyFileTransacted(string lpExistingFileName, string lpNewFileName, CopyProgressRoutineHandler lpProgressRoutine,
														  IntPtr lpData/*LPVOID*/, bool pbCancel, uint dwCopyFlags, IntPtr hTransaction);

		[DllImport("kernel32.dll")]
		internal static extern bool MoveFileTransacted(string lpExistingFileName, string lpNewFileName, CopyProgressRoutineHandler lpProgressRoutine,
															IntPtr lpData, uint dwFlags, IntPtr hTransaction);

		[DllImport("kernel32.dll")]
		internal static extern bool RemoveDirectoryTransacted(string lpPathName, IntPtr hTransaction);


		[DllImport("kernel32.dll")]
		internal static extern bool CreateDirectoryTransacted(string lpTemplateDirectory, string lpNewDirectory, SECURITY_ATTRIBUTES lpSecurityAttributes,
																IntPtr hTransaction);

		[DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
		static extern int OpenFile([MarshalAs(UnmanagedType.LPStr)]string lpFileName, out OFSTRUCT lpReOpenBuff, OpenFileFlags uStyle);


		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool CloseHandle(IntPtr hObject);

		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool CancelIO(IntPtr hFile);

		[DllImport("kernel32.dll")]
		internal static extern bool LockFile(IntPtr hFile, uint dwFileOffsetLow, uint dwFileOffsetHigh, uint nNumberOfBytesToLockLow,
												uint nNumberOfBytesToLockHigh);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool DeleteFile(string filename);

		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		static extern bool ReplaceFile(string lpReplacedFileName, string lpReplacementFileName, string lpBackupFileName,
										ReplaceFileFlags dwReplaceFlags, IntPtr lpExclude, IntPtr lpReserved);

		[DllImport("kernel32.dll")]
		static extern FileType GetFileType(IntPtr hFile);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool FlushFileBuffers(IntPtr hFile);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool CopyFile(string source, string destanation, bool failIfExist);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.I4)]
		internal static extern int GetFileAttributes(string source);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.I4)]
		internal static extern Int32 GetFileSize(IntPtr hFile, int fileSize);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.I4)]
		internal static extern Int32 GetTempPath(int dwBuff, StringBuilder buff);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool MoveFile(string from, string to);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool RemoveDirectory(string directory);

		[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern int SearchPath(string path, string fileName, string extension, int numBufferChars,
												StringBuilder buffer, int[] filePart);

		[DllImport("kernel32.dll")]
		internal static extern IntPtr CreateEvent(IntPtr lpEventAttributes, Boolean bManualReset, Boolean bInitialState, String lpName);

		[DllImport("kernel32.dll", SetLastError = true)]
		internal static extern Boolean WriteFile(IntPtr fFile, Byte[] lpBuffer, UInt32 nNumberOfBytesToWrite,
												out UInt32 lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);

		[DllImport("kernel32.dll", SetLastError = true)]
		internal static extern Boolean ReadFile(IntPtr hFile, Byte[] lpBuffer, UInt32 nNumberOfBytesToRead,
												out UInt32 nNumberOfBytesRead, ref OVERLAPPED lpOverlapped);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool SetEndOfFile(IntPtr hFile);

		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		static extern bool GetDiskFreeSpace(string lpRootPathName, out uint lpSectorsPerCluster, out uint lpBytesPerSector,
											out uint lpNumberOfFreeClusters, out uint lpTotalNumberOfClusters);


		public static string GetTempPathApi()
		{
			int lenght = 64;
			StringBuilder sb = new StringBuilder(lenght);
			GetTempPath(lenght, sb);
			return sb.ToString();
		}

		public static string GetShortPathApi(string fullPath)
		{
			int len = 64;
			StringBuilder sb = new StringBuilder(len);
			GetShortPathName(fullPath, ref sb, len);
			return sb.ToString();
		}

		public static void CreateFile()
		{
			SECURITY_ATTRIBUTES atr = new SECURITY_ATTRIBUTES();

			IntPtr hFile = CreateFile(@"C:\12453.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, ref atr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, new IntPtr());
			CloseHandle(hFile);
			Marshal.FreeHGlobal(hFile);
		}

		public static bool WriteFrame(IntPtr handle, byte[] SendArr)
		{
			if (handle == (IntPtr)INVALID_HANDLE_VALUE) return false;
			if (SendArr == null || SendArr.Length == 0) return false;

			uint BytesWritten = 0;

			// Если результат WriteFile() равен False, то это значит, что на момент возврата управления передача массива байтов еще не закончилась.
			// Поэтому код ошибки выполнения WriteFile() в данном случае должен быть равен ERROR_IO_PENDING

			OVERLAPPED OverlappedWrite = new OVERLAPPED();
			OverlappedWrite.hEvent = CreateEvent(IntPtr.Zero, false, false, null);

			if (!WriteFile(handle, SendArr, (uint)SendArr.Length, out BytesWritten, ref OverlappedWrite))
			{
				Int32 Res = Marshal.GetLastWin32Error();
				if (Res != ERROR_IO_PENDING)
				{
					CancelIOApi(handle);
					return false;
				}
			}
			return true;
		}

		public static void CancelIOApi(IntPtr hFile)
		{
			CancelIO(hFile);
		}

		public struct DiskFreeSpaceInfo
		{
			public uint SectorsPerCluster;
			public uint BytesPerSector;
			public uint NumberOfFreeClusters;
			public uint TotalNumberOfClusters;
		}

		public static DiskFreeSpaceInfo GetDiskFreeSpaceApi(string diskName)
		{
			uint SectorsPerCluster;
			uint BytesPerSector;
			uint NumberOfFreeClusters;
			uint TotalNumberOfClusters;

			GetDiskFreeSpace(diskName, out SectorsPerCluster, out BytesPerSector, out NumberOfFreeClusters, out TotalNumberOfClusters);
			DiskFreeSpaceInfo info = new DiskFreeSpaceInfo();
			info.BytesPerSector = BytesPerSector;
			info.NumberOfFreeClusters = NumberOfFreeClusters;
			info.TotalNumberOfClusters = TotalNumberOfClusters;
			info.SectorsPerCluster = SectorsPerCluster;
			return info;
		}

		public class FileTimeApi
		{
			public DateTime CreationTime;
			public DateTime LastAccessTime;
			public DateTime LastWriteTime;
		}

		public static FileTimeApi GetFileTime(IntPtr hFile)
		{
			FILETIME ftCreationTime = new FILETIME();
			FILETIME ftLastAccessTime = new FILETIME();
			FILETIME ftLastWriteTime = new FILETIME();

			DateTime CreationTime = DateTime.MinValue;
			DateTime LastAccessTime = DateTime.MinValue;
			DateTime LastWriteTime = DateTime.MinValue;

			if (GetFileTime(hFile, ref ftCreationTime, ref ftLastAccessTime, ref ftLastWriteTime) != true)
				return null;

			CreationTime = DateTime.FromFileTimeUtc((((long)ftCreationTime.dwHighDateTime) << 32) | ((uint)ftCreationTime.dwLowDateTime));
			LastAccessTime = DateTime.FromFileTimeUtc((((long)ftLastAccessTime.dwHighDateTime) << 32) | ((uint)ftLastAccessTime.dwLowDateTime));
			LastWriteTime = DateTime.FromFileTimeUtc((((long)ftLastWriteTime.dwHighDateTime) << 32) | ((uint)ftLastWriteTime.dwLowDateTime));

			return new FileTimeApi()
			{
				CreationTime = CreationTime,
				LastAccessTime = LastAccessTime,
				LastWriteTime = LastWriteTime
			};

		}

		public static FileTimeApi GetFileTime(string hFile)
		{
			FILETIME ftCreationTime = new FILETIME();
			FILETIME ftLastAccessTime = new FILETIME();
			FILETIME ftLastWriteTime = new FILETIME();

			DateTime CreationTime = DateTime.MinValue;
			DateTime LastAccessTime = DateTime.MinValue;
			DateTime LastWriteTime = DateTime.MinValue;

			SECURITY_ATTRIBUTES sec = new SECURITY_ATTRIBUTES();
			IntPtr ptr = CreateFile(hFile, GENERIC_READ, FILE_SHARE_READ, ref sec, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
			if (ptr.ToInt32() == INVALID_HANDLE_VALUE)
				return null;

			if (GetFileTime(ptr, ref ftCreationTime, ref ftLastAccessTime, ref ftLastWriteTime) != true)
				return null;

			CreationTime = DateTime.FromFileTimeUtc((((long)ftCreationTime.dwHighDateTime) << 32) | ((uint)ftCreationTime.dwLowDateTime));
			LastAccessTime = DateTime.FromFileTimeUtc((((long)ftLastAccessTime.dwHighDateTime) << 32) | ((uint)ftLastAccessTime.dwLowDateTime));
			LastWriteTime = DateTime.FromFileTimeUtc((((long)ftLastWriteTime.dwHighDateTime) << 32) | ((uint)ftLastWriteTime.dwLowDateTime));

			if (ptr != IntPtr.Zero && ptr.ToInt32() != INVALID_HANDLE_VALUE)
				CloseHandle(ptr);

			return new FileTimeApi()
			{
				CreationTime = CreationTime,
				LastAccessTime = LastAccessTime,
				LastWriteTime = LastWriteTime
			};
		}

		public static FileType GetFileTypeApi(IntPtr hfile)
		{
			if (hfile == IntPtr.Zero || hfile.ToInt32() == INVALID_HANDLE_VALUE)
				return FileType.FileTypeUnknown;
			else
				return (FileType)GetFileType(hfile);
		}

		public static FileStream CreateTransactedFile(string fileName)
		{
			IKernelTransaction ktx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
			IntPtr txHandle;
			ktx.GetHandle(out txHandle);

			IntPtr fileHandle = CreateFileTransacted(fileName, GENERIC_WRITE, FILE_SHARE_NONE, IntPtr.Zero, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
														IntPtr.Zero, txHandle, IntPtr.Zero, IntPtr.Zero);
			return new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle(fileHandle, true), FileAccess.Write);
		}

		public static void UseTransactionInFiles(string fname)
		{
			if (string.IsNullOrEmpty(fname))
				return;
			if (File.Exists(fname))
				File.Delete(fname);

			using (TransactionScope scope = new TransactionScope())
			using (FileStream stream = CreateTransactedFile(fname))
			using (StreamWriter writer = new StreamWriter(stream))
			{
				writer.WriteLine("Hello, World!");

				// To commit the transaction use the followind line
				scope.Complete();
			}
		}

	}
}
